local g_verbose = false
local g_execv = nil -- 非 script lifecyle hook scope, 非 on_run 函数无法获取到 os 模块的扩展函数
local g_raise = nil
local g_mode = nil
local g_target = nil
local g_infoplist = nil
local g_platform_arch = {
  iphoneos = { "arm64", "armv7s", "armv7"  }, -- xcframework 不支持 "i386", "x86_64" 
  -- watchos = { "arm64_32", "armv7k" },
  -- appletvos = { "arm64" },
  -- iphonesimulator = { "arm64", "x86_64" },
  -- watchsimulator = { "arm64", "x86_64" },
  -- appletvsimulator = { "arm64", "x86_64" },
  macosx = { "arm64", "x86_64" },
}

task("xcfw")
set_category("plugin")

set_menu({
  usage = "xmake xcframework [target]",
  description = "build xcframework",
  options = {
    { 't', 'target', 'v', nil, "target platform and arch. iphoneos:arm64,armv7s;macosx:arm64,x86_64 " },
    {'m', 'mode', 'v', nil, 'use release or debug mode artifact. release is default.'},
    {'i', 'infoplist', 'v', nil, 'Info.plist file for framework, must provide.'},
    {'v', "verbose", "k", nil, "verbose mode."},
  }
})

local function printTable(t, indent)
  indent = indent or 0
  local indentStr = string.rep("  ", indent)
  
  if type(t) ~= "table" then
      print(indentStr .. tostring(t))
      return
  end

  print(indentStr .. "{")
  for k, v in pairs(t) do
      local keyStr = tostring(k)
      if type(v) == "table" then
          print(indentStr .. "  " .. keyStr .. " = ")
          printTable(v, indent + 1)
      else
          local valueStr = tostring(v)
          print(indentStr .. "  " .. keyStr .. " = " .. valueStr)
      end
  end
  print(indentStr .. "}")
end

local function logger(msg)
  if g_verbose then
    print(msg)
  end
end

local function parse_target_str(target_str)
    local m_target = {}
    -- 验证字符串格式
    if type(target_str) ~= "string" or target_str:gsub("%s", "") == "" then
        g_raise("[🍄] Target string cannot be empty")
    end
    
    -- 按分号分割平台
    for platform_str in target_str:gmatch("%s*([^;]+)%s*") do
        if platform_str == "" then goto continue end

        -- 按冒号分割平台和架构（允许平台包含任意非空字符）
        local platform, archs = platform_str:match("^%s*([^:]-)%s*:%s*(.-)%s*$")
        if not platform or platform == "" then
            g_raise(("[🍄] Invalid platform format in segment: '%s'"):format(platform_str))
        end

        -- 验证平台是否在支持列表
        if not g_platform_arch[platform] then
            g_raise(("[🍄] Unsupported platform: '%s'"):format(platform))
        end

        -- 处理架构部分
        local arch_list = {}
        if not archs or archs:gsub("%s", "") == "" then
            g_raise(("[🍄] No architectures specified for platform '%s'"):format(platform))
        end

        -- 分割架构并验证
        for arch in archs:gmatch("%s*([^,]+)%s*") do
            local valid = false
            for _, supported_arch in ipairs(g_platform_arch[platform]) do
                if arch == supported_arch then
                    valid = true
                    break
                end
            end
            if not valid then
                g_raise(("[🍄] Unsupported arch '%s' for platform '%s'"):format(arch, platform))
            end
            table.insert(arch_list, arch)
        end

        m_target[platform] = arch_list
        ::continue::
    end

    local has_content = false
    for _ in pairs(m_target) do
        has_content = true
        break
    end
    if not has_content then
        g_raise("[🍄] No valid platform configurations found")
    end

    return m_target
end

local function execute(cmd)
  logger("[🐯] Executing: " .. cmd)
  local result = g_execv(cmd)
  if result ~= 0 then
    g_raise("[🍄] Command failed: " .. cmd)
  end
  return result
end

on_run(function ()
  import("core.base.option")
  g_verbose = option.get("verbose") or false
  g_mode = option.get("mode") or "release"
  g_infoplist = option.get("infoplist")
  local target = option.get("target")
  g_execv = os.execv
  g_raise = os.raise

  if not g_infoplist then
    g_raise("infoplist not specified")
  end
  
  local builddir = path.join(os.workingdir(), "build")
  if not os.isdir(builddir) then
    g_raise("build directory not exist, please run xmake build first.")
  end

  local xcfw_out_dir = path.join(builddir, "xcframework")
  os.mkdir(xcfw_out_dir)

  if target == nil then 
    logger("🍄 no target specified, use default setting:  iphoneos:arm64,armv7s,armv7;macosx:arm64,x86_64")
    g_target = g_platform_arch
  else
    g_target = parse_target_str(target)
  end
  if g_verbose then 
    printTable(g_target)
  end

  logger("[🏃] scan target archive...")

  for plat, archs in pairs(g_target) do
    for _, arch in ipairs(archs) do
      local fw = path.join(builddir, plat, arch, g_mode, "qjs.framework")
      logger('[💨] ' .. fw .. " exists: " .. tostring(os.isdir(fw)))
      if not os.isdir(fw) then
        g_raise("[🍄] framework not found: " .. fw)
      end 
    end
  end

  local xcfw_param = ''

  for plat, archs in pairs(g_target) do
    local plat_fat_fw_dir = path.join(xcfw_out_dir, plat .. '-universal', 'qjs.framework')
    os.mkdir(plat_fat_fw_dir)
    os.mkdir(path.join(plat_fat_fw_dir, 'Versions', 'A', 'Headers'))
    os.mkdir(path.join(plat_fat_fw_dir, 'Versions', 'A', 'Resources'))
    os.cp(g_infoplist, path.join(plat_fat_fw_dir, 'Versions', 'A', 'Info.plist'))

    xcfw_param = xcfw_param .. ' -framework ' .. plat_fat_fw_dir
    local fws = ''
    local header_copied = false

    for _, arch in ipairs(archs) do
      fws = fws .. path.join(builddir, plat, arch, g_mode, "qjs.framework/qjs") .. " "
      if not header_copied then
        os.cp(path.join(builddir, plat, arch, g_mode, "qjs.framework/Headers/*.h"), path.join(plat_fat_fw_dir, 'Versions', 'A', 'Headers'))
        header_copied = true
      end
    end

    local fat_fw = path.join(plat_fat_fw_dir, 'Versions', 'A', 'qjs')
    execute('lipo -create ' .. fws .. ' -output ' .. fat_fw)
    logger('[🌈] check generated fat framework')
    execute('lipo -info ' .. fat_fw)

    os.ln(path.join(plat_fat_fw_dir, 'Versions', 'A', 'Headers'), path.join(plat_fat_fw_dir, "Headers"))
    os.ln(path.join(plat_fat_fw_dir, 'Versions', 'A', 'Resources'), path.join(plat_fat_fw_dir, "Resources"))
    os.ln(path.join(plat_fat_fw_dir, 'Versions', 'A', 'qjs'), path.join(plat_fat_fw_dir, "qjs"))
    os.ln(path.join(plat_fat_fw_dir, 'Versions', 'A'), path.join(plat_fat_fw_dir, "Versions", "Current"))
  end

  local dest = path.join(xcfw_out_dir, "qjs.xcframework")
  logger('[🌈] generate xcframework')
  execute('xcodebuild -create-xcframework -output ' .. dest .. ' ' .. xcfw_param)
  print("🌹XCFramework created at: " .. dest)
end)